﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using TmxLib;

namespace TmxEditor
{
    public class SnaphotLoader : MarshalByRefObject
    {
        public static void Load(SnapshotInfo snapshot, string atAssemblyName, string atTypeName)
        {
            AppDomain domain =
                AppDomain.CreateDomain(
                    "Preview Domain of "
                    );

            SnaphotLoader loader = 
            (SnaphotLoader) domain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, typeof (SnaphotLoader).FullName);

            Debug.WriteLine("Working Folder Before Load: " + snapshot.WorkingFolder);

            loader.LoadImpl(snapshot, atAssemblyName, atTypeName);
        }


        private SnapshotInfo _snapshot;

        private void LoadImpl(SnapshotInfo snapshot, string atAssemblyName, string atTypeName)
        {
            if (!snapshot.HasManifest)
                throw new ArgumentException("Could not find snapshot manifest");

            _snapshot = snapshot;

            Debug.WriteLine(string.Empty);
            Debug.WriteLine("Load snapshot from '" + snapshot.WorkingFolder + "'");
            SnapshotBinaryItemInfoCollection asmInfoList = snapshot.AssembliesInfo;
            AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
            foreach (SnapshotBinaryItemInfo asmInfo in asmInfoList)
            {
                string asmFile = Path.Combine(snapshot.WorkingFolder, asmInfo.FileName);
                Debug.WriteLine("Load '" + asmInfo.AssemblyName + "' from " + asmFile);
                Assembly asm = LoadWithSattelite(asmFile);
                try
                {
                    asm.GetTypes();
                    Debug.WriteLine("Done: " + asmInfo.AssemblyName);
                }
                catch(Exception ex)
                {
                    Debug.WriteLine("Failed: " + asmInfo.AssemblyName);
                    Debug.WriteLine(ex.ToString());
                }
                
            }
            // AppDomain.CurrentDomain.AssemblyResolve -= new ResolveEventHandler(CurrentDomain_AssemblyResolve);

            // AppDomain.CurrentDomain.
            Test1(atAssemblyName, atTypeName);



        }

        Assembly LoadWithSattelite(string fileName)
        {
            Assembly asm = Assembly.LoadFile(fileName);
            Debug.WriteLine("Loaded assembly '" + asm.GetName().FullName + "' from '" + fileName + "'");

            DirectoryInfo dir = new DirectoryInfo(Path.GetDirectoryName(fileName));
            DirectoryInfo[] subs = dir.GetDirectories();
            foreach(DirectoryInfo sub in subs)
            {
                Debug.WriteLine("Try sat [" + sub.Name + "] from " + dir.FullName);
                string sat = Path.Combine(sub.FullName, Path.GetFileNameWithoutExtension(fileName) + ".resources.dll");
                if (File.Exists(sat))
                {
                    Assembly asm2 = Assembly.LoadFile(sat);
                    Assembly asm3 = asm.GetSatelliteAssembly(new CultureInfo(sub.Name));
                    Debug.WriteLine("  Found satellite [" + sub.Name + "]: " + asm2.GetName().FullName);
                }
            }

            return asm;
        }

        Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            SnapshotBinaryItemInfoCollection asmInfoList = _snapshot.AssembliesInfo;
            AssemblyName an = new AssemblyName(args.Name);
            if (an.CultureInfo.ToString().Length > 0)
            {
                Debug.WriteLine("Resolve satelite: " + an.FullName);
            }
                

            foreach (SnapshotBinaryItemInfo info in asmInfoList)
            {
                if (info.AssemblyName == args.Name)
                {
                    string file = Path.Combine(_snapshot.WorkingFolder, info.FileName);
                    Assembly asm = LoadWithSattelite(file);
                    return asm;
                }

                else if (an.CultureInfo.ToString().Length > 0)
                {
                    AssemblyName cn = new AssemblyName(info.AssemblyName);
                    bool isTokenEqual =
                        Convert.ToBase64String(an.GetPublicKeyToken() == null ? new byte[0] : an.GetPublicKeyToken())
                        == Convert.ToBase64String(cn.GetPublicKeyToken() == null ? new byte[0] : cn.GetPublicKeyToken());

                    // if (an.Name.StartsWith(cn.Name)) Debugger.Break();

                    if (
                        an.Name == cn.Name + ".resources" 
                        && an.Version == cn.Version 
                        && isTokenEqual
                        && cn.CultureInfo.ToString().Length == 0
                        && an.CultureInfo.ToString().Length > 0)
                    {
                        string sp = Path.Combine(Path.GetDirectoryName(info.FileName), an.CultureInfo.ToString());
                        sp = Path.Combine(_snapshot.WorkingFolder, sp);
                        string sf = Path.Combine(sp, an.Name + ".dll");
                        if (File.Exists(sf))
                        {
                            Debug.WriteLine("Sattelite " + args.Name + " loaded from: " + sf);
                            Assembly sat = Assembly.LoadFrom(sf);
                            return sat;
                        }
                        else
                        {
                            Debug.WriteLine("Sattelite " + args.Name + " NOT FOUND: " + sf);
                            // return null;
                        }
                    }
                    else
                    {
                        // Debug.WriteLine("NOT EXPECT: " + cn.FullName);
                    }
                    
                }
            }

            Debug.WriteLine("Failed to load '" + args.Name + "'");
            return Assembly.GetExecutingAssembly();
        }

        void Test1(string atAssemblyName, string atTypeName)
        {
            Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
            Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US");

            List<Assembly> loaded = new List<Assembly>(AppDomain.CurrentDomain.GetAssemblies());
            loaded.Sort(delegate(Assembly x, Assembly y) { return x.GetName().Name.CompareTo(y.GetName().Name); });
            Debug.WriteLine("");
            Debug.WriteLine("Loaded:");
            foreach (Assembly assembly in loaded)
            {
                Debug.WriteLine(assembly.GetName().FullName + " from " + assembly.CodeBase);
            }


            Assembly asm = this[new AssemblyName(atAssemblyName)];
            if (asm == null)
                throw new ArgumentException("Failed to get assembly " + atAssemblyName);

            Type t = asm.GetType(atTypeName, true, false);
            DesignSurface ds = new DesignSurface();
            ds.BeginLoad(t); // loads the Form
            Control view = ds.View as Control;
            Form f = new Form();
            f.Controls.Add(view);
            view.Dock = DockStyle.Fill;
            Application.Run(f);
        }

        Assembly this[AssemblyName assemblyName]
        {
            get
            {
                Assembly[] loaded = AppDomain.CurrentDomain.GetAssemblies();
                foreach (Assembly assembly in loaded)
                    if (assembly.GetName().FullName == assemblyName.FullName)
                        return assembly;

                return null;
            }
        }

    }
}
